#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h> /* close() */
#include <string.h> /* memset() */
#include "consts.h"

#define SERVER_POR_ARG 1

typedef struct user
{
	char* userName;
	char* groups[MAX_GROUPS];
	struct sockaddr_in sock;
} user_t;

char* CurrUser = NULL;
char MsgBuff[MAX_MSG_LEN] = {0};
char* activeGroups[MAX_GROUPS] = {0};
user_t users[MAX_USERS];
struct sockaddr_in cliAddr, servAddr;
int sd, rc, n, flags = 0;

user_t * getCurrUser()
{
	int i;
	for (i = 0 ; i < MAX_USERS ; i++)
	{
		if (users[i].userName && !strcmp(users[i].userName,CurrUser))
		{
			return &users[i];
		}
	}
	return NULL;
}

int isUserInGroup(user_t * user, char* group)
{
	int j;
	for (j = 0; j < MAX_GROUPS; ++j)
	{
		if(user->groups[j] &&
				!strncmp(user->groups[j],group,strlen(group)))
		{
			return 1;
		}
	}
	return 0;
}

int groupExists(char* group)
{
	int i;
	for (i = 0; i < MAX_USERS; ++i)
	{
		if (!users[i].userName)
			continue;
		if (isUserInGroup(&users[i],group))
			return 1;
	}
	return 0;
}

void deleteEmptyGroups()
{
	int i;
	for (i = 0; i < MAX_GROUPS; ++i)
	{
		char * group = activeGroups[i];
		if (!group)
			continue;
		if (!groupExists(group))
		{
			free(activeGroups[i]);
			activeGroups[i] = NULL;
		}
	}
}

int addGroupToUser(char* group)
{
	int i,index = -1;
	user_t * user = getCurrUser();
	if (!user)
		return 0;
	for (i = 0; i < MAX_GROUPS; ++i)
	{
		if (!user->groups[i])
		{
			index = i;
		}
		else if (!strncmp(user->groups[i],group,strlen(group)))
		{
			return 1;
		}
	}
	if (index < 0)
		return 0;
	user->groups[index] = (char*)malloc(sizeof(char)*(strlen(group)+1));
	strncpy(user->groups[index],group,strlen(group)+1);
	return 1;
}

char* executeJoin(char* group)
{
	int i,index = -1;
	if (!group || strlen(group) < 1)
		return RES_FAIL;
	for (i = 0; i < MAX_GROUPS; ++i)
	{
		if (!activeGroups[i])
			index = i;
		else if (!strncmp(group,activeGroups[i],strlen(group)))
		{
			index = i;
			break;
		}
	}
	if (index < 0 || !addGroupToUser(group))
		return RES_FAIL;
	if (!activeGroups[index])
	{
		activeGroups[index] = (char*)malloc(sizeof(char)*(strlen(group)+1));
		strncpy(activeGroups[index],group,strlen(group)+1);
	}
	return RES_OK;
}

char* executeLeave(char* group)
{
	int i;
	user_t * user = getCurrUser();
	if (!user || !group || strlen(group) < 1)
		return RES_FAIL;
	for (i = 0 ; i < MAX_GROUPS ; i++)
	{
		if (user->groups[i] && !strncmp(group,user->groups[i],strlen(group)))
		{
			free(user->groups[i]);
			user->groups[i] = NULL;
			deleteEmptyGroups();
			return RES_OK;
		}
	}
	return RES_FAIL;
}

char* executeExit()
{
	user_t* user = getCurrUser();
	int i;

	if (user)
	{
		for (i = 0 ; i < MAX_GROUPS ; i++)
		{
			executeLeave(user->groups[i]);
		}

		free(user->userName);
		user->userName = NULL;
	}
	deleteEmptyGroups();
	return RES_OK;
}

int sendMessageToUser(user_t * user,char * message)
{
	return sendto(sd,message,strlen(message),
			flags,(struct sockaddr *)&user->sock,sizeof(user->sock));
}

int trySendToUser(char * user, char * message)
{
	int i;
	for (i = 0; i < MAX_USERS; ++i)
	{
		if (users[i].userName && strncmp(CurrUser,users[i].userName,strlen(CurrUser)) &&
				!strncmp(user,users[i].userName,strlen(user)))
		{
			if (sendMessageToUser(&users[i],message) > 0)
				return 1;
		}
	}
	return 0;
}

int trySendToGroup(char * group, char * message)
{
	int i,res = 0;
	if (!isUserInGroup(getCurrUser(),group))
		return res;
	for (i = 0; i < MAX_USERS; ++i)
	{
		if (users[i].userName && strncmp(CurrUser,users[i].userName,strlen(CurrUser)) &&
				isUserInGroup(&users[i],group)
		)
		{
			if (sendMessageToUser(&users[i],message) > 0)
				res = 1;
		}
	}
	return res;
}

char * executeMessage(char * target, char * message)
{
	char formattedMsg[MAX_MSG_LEN] = {0};
	if (!target || !strlen(target )
				|| !message || !strlen(message))
		return RES_FAIL;
	char * res = RES_FAIL;
	strncpy(formattedMsg,CurrUser,strlen(CurrUser)+1);
	strncat(formattedMsg," ",1);
	strncat(formattedMsg,message,strlen(message));
	if(trySendToUser(target,formattedMsg))
			res = RES_OK;
	if(trySendToGroup(target,formattedMsg))
			res = RES_OK;
	return res;
}

char* executeUsers(char* group)
{
	char groupTmp[MAX_MSG_LEN] = {0};
	if (group)
		strncpy(groupTmp,group,strlen(group)+1);
	memset(MsgBuff,0x0,MAX_MSG_LEN);
	int i = 0;
	for (i = 0; i < MAX_USERS; ++i)
	{
		if (users[i].userName && ((!group) || isUserInGroup(&users[i],groupTmp)))
		{
			strncat(MsgBuff,users[i].userName,strlen(users[i].userName));
			if (strlen(MsgBuff))
				strncat(MsgBuff," ",strlen(" "));
		}
	}
	return MsgBuff;
}

char* executeGroups()
{
	memset(MsgBuff,0x0,MAX_MSG_LEN);
	int i = 0;
	for (i = 0; i < MAX_GROUPS; ++i)
	{
		char* group = activeGroups[i] ? activeGroups[i] : "";
		strncat(MsgBuff,group,strlen(group));
		if (strlen(group))
			strncat(MsgBuff," ",strlen(" "));
	}
	return MsgBuff;
}

char* safeString(char* s)
{
	return (s == NULL) ? "NULL" : s;
}

char* executeNew(char* port)
{
	int i = 0,emptySlot = -1;
	for (i = 0 ; i < MAX_USERS ; i++)
	{
		if (users[i].userName && !strcmp(users[i].userName,CurrUser))
		{
			// User already exists
			return RES_FAIL;
		}
		if (!users[i].userName)
			emptySlot = i;
	}
	if (emptySlot < 0)
	{
		// Can't add, queue full
		return RES_FAIL;
	}

	users[emptySlot].userName = (char*)malloc(sizeof(char)*(strlen(CurrUser)+1));
	strncpy(users[emptySlot].userName,CurrUser,strlen(CurrUser)+1);

	users[emptySlot].sock.sin_family = cliAddr.sin_family;
	memcpy((void *) &users[emptySlot].sock.sin_addr,
			&cliAddr.sin_addr, sizeof(struct in_addr));
	users[emptySlot].sock.sin_port = htons(atoi(port));

	for (i = 0; i < MAX_GROUPS; ++i) {
		users[emptySlot].groups[i] = NULL;
	}
	return RES_OK;
}

char* executeCommand(char* cmd)
{
	char * cmd_name;
	CurrUser = strtok (cmd," ");
	cmd_name = strtok (NULL," ");
	if (!strcmp(cmd_name,CMD_NEW))
	{
		return executeNew(strtok (NULL, " "));
	}
	if (!strcmp(cmd_name,CMD_JOIN))
	{
		return executeJoin(strtok (NULL, " "));
	}
	else if (!strcmp(cmd_name,CMD_LEAVE))
	{
		return executeLeave(strtok (NULL, " "));
	}
	else if (!strcmp(cmd_name,CMD_EXIT))
	{
		return executeExit();
	}
	else if (!strcmp(cmd_name,CMD_MESSAGE))
	{
		char* target = strtok (NULL, " ");
		return executeMessage(target,target + strlen(target) + 1);
	}
	else if (!strcmp(cmd_name,CMD_GROUPS))
	{
		return executeGroups();
	}
	else if (!strcmp(cmd_name,CMD_USERS))
	{
		return executeUsers(strtok (NULL, " "));
	}
	return ("Invalid command\n");
}

void printState()
{
	int i,j;
	printf("users:\n");
	for (i = 0 ; i < MAX_USERS ; i++)
	{
		printf("users[%d] = %s\n",i,safeString(users[i].userName));
//		if (users[i].userName)
			for (j = 0; j < MAX_GROUPS; ++j)
			{
	//			if (users[i].groups[j])
				printf("\t\t%s\n",safeString(users[i].groups[j]));
			}
	}
	printf("groups:\n");
	for (j = 0; j < MAX_GROUPS; ++j)
	{
		if (activeGroups[j])
			printf("%s\n",activeGroups[j]);
	}
}

int main(int argc, char *argv[]) {

  int i;
  unsigned int cliLen;
  char* res;
  int port;

  /* check command line args */
    if(argc!=2) {
      printf("Invalid arguments!\n");
      printf("usage: %s <server_port>\n", argv[0]);
      exit(1);
    }
  port = atoi(argv[SERVER_POR_ARG]);

  /* socket creation */
  sd=socket(AF_INET, SOCK_DGRAM, 0);
  if(sd<0) {
    printf("Cannot open socket \n");
    exit(1);
  }

  /* bind local server port */
  servAddr.sin_family = AF_INET;
  servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servAddr.sin_port = htons(port);
  rc = bind (sd, (struct sockaddr *) &servAddr,sizeof(servAddr));
  if(rc<0) {
    printf("Cannot bind port number %u \n", port);
    exit(1);
  }

  for (i = 0; i < MAX_USERS; ++i) {
  		users[i].userName = NULL;
  	}

  printf("Waiting for data on port UDP %u\n",port);

  /* server infinite loop */
  while(1) {

    /* init buffer */
    memset(MsgBuff,0x0,MAX_MSG_LEN);

    /* receive message */
    cliLen = sizeof(cliAddr);
    n = recvfrom(sd, MsgBuff, MAX_MSG_LEN, flags,
		 (struct sockaddr *) &cliAddr
		 , &cliLen);

    if(n<0) {
      printf("cannot receive data \n");
      continue;
    }

    /* print received message */
    printf("%s:%u : %s \n",inet_ntoa(cliAddr.sin_addr),
	   ntohs(cliAddr.sin_port),MsgBuff);


    res = executeCommand(MsgBuff);
    sendto(sd,res,strlen(res),flags,(struct sockaddr *)&cliAddr,cliLen);

//    printState();
	fflush(stdout);

  }

return 0;

}
